﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SpeakupBackend.Themes;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

namespace SpeakupTests.SpeakupBackend.Themes
{
    [TestClass]
    public class LevelGroupTests
    {
        static List<Tuple<int, int>> InvalidLevelGroups = new List<Tuple<int, int>>();

        [ClassInitialize]
        public static void ClassInitialize(TestContext context)
        {
            DefaultLevelGroupFactory factory = new DefaultLevelGroupFactory();

            foreach (Tuple<int, int> tuple in factory.ValidLevelGroups)
            {
                //inversed
                if (tuple.Item1 != tuple.Item2)
                {
                    InvalidLevelGroups.Add(new Tuple<int, int>(tuple.Item2, tuple.Item1));
                }

                //invalid level number
                InvalidLevelGroups.Add(new Tuple<int, int>(Level.MinValidLevelNumber - 1, tuple.Item2));
                InvalidLevelGroups.Add(new Tuple<int, int>(tuple.Item1, Level.MaxValidLevelNumber + 1));
            }
        }

        [TestMethod]
        public void CreateLevelGroup()
        {
            //create thLevelGroup with valid thLevelGroup number
            foreach (var tuple in LevelGroup.DefaultFactory.ValidLevelGroups)
            {
                PrivateObject privateObject = new PrivateObject(typeof(LevelGroup), tuple.Item1, tuple.Item2);
                LevelGroup levelGroup = (LevelGroup)privateObject.Target;
                Assert.AreEqual(tuple.Item1, levelGroup.MinLevel);
                Assert.AreEqual(tuple.Item2, levelGroup.MaxLevel);
            }

            //verify that invalid levelGroups can not be created
            foreach (var tuple in InvalidLevelGroups)
            {
                bool thrown = false;
                try
                {
                    PrivateObject privateObject = new PrivateObject(typeof(LevelGroup), tuple.Item1, tuple.Item2);
                }
                catch (TargetInvocationException exception)
                {
                    if (exception.InnerException.GetType() == typeof(ArgumentException))
                    {
                        thrown = true;
                    }
                }

                Assert.AreEqual(true, thrown);
            }
        }

        [TestMethod]
        public void LevelGroupGetLevels()
        {
            foreach (LevelGroup lg in LevelGroup.DefaultFactory.GetLevelGroups())
            {
                IList<Level> levels = lg.GetLevels();
                for (int i = lg.MinLevel; i <= lg.MaxLevel; i++)
                {
                    Assert.IsTrue(levels.Contains(Level.GetLevel(i)));
                }
            }
        }

        
        [TestMethod]
        public void LevelGroupToString()
        {
            //verify that each thLevelGroup can be deserializedLevelGroup from string.
            foreach (var tuple in LevelGroup.DefaultFactory.ValidLevelGroups)
            {
                LevelGroup original = LevelGroup.DefaultFactory.GetLevelGroup(tuple.Item1, tuple.Item2);
                LevelGroup fromString = LevelGroup.DefaultFactory.GetLevelGroup(original.ToString());

                Assert.AreEqual(original.MinLevel, fromString.MinLevel);
                Assert.AreEqual(original.MaxLevel, fromString.MaxLevel);
            }
        }

        [TestMethod]
        public void LevelGroupContainsLevel()
        {
            foreach (var group in LevelGroup.DefaultFactory.GetLevelGroups())
            {
                for (int i = Level.MinValidLevelNumber; i <= Level.MaxValidLevelNumber; i++)
                {
                    bool shouldContainLevel = (i >= group.MinLevel && i <= group.MaxLevel);
                    Level level = Level.GetLevel(i);
                    Assert.AreEqual(shouldContainLevel, group.ContainsLevel(level));
                }
            }
        }
        
        [TestMethod]
        public void LevelGroupObjectEquals()
        {
            //verify that all equal object return true for Equals (non-generic)
            foreach (var tuple in LevelGroup.DefaultFactory.ValidLevelGroups)
            {
                PrivateObject privateLevelGroupA = new PrivateObject(typeof(LevelGroup), tuple.Item1, tuple.Item2);
                PrivateObject privateLevelGroupB = new PrivateObject(typeof(LevelGroup), tuple.Item1, tuple.Item2);

                Assert.IsTrue(privateLevelGroupA.Target.Equals(privateLevelGroupB.Target));
            }

            //verify that all non-equal return false for Equals (non-generic)
            foreach (var i in LevelGroup.DefaultFactory.ValidLevelGroups)
            {
                foreach (var j in LevelGroup.DefaultFactory.ValidLevelGroups)
                {
                    if (i == j) continue;

                    PrivateObject privateLevelGroupA = new PrivateObject(typeof(LevelGroup), i.Item1, i.Item2);
                    PrivateObject privateLevelGroupB = new PrivateObject(typeof(LevelGroup), j.Item1, j.Item2);

                    Assert.IsFalse(privateLevelGroupA.Target.Equals(privateLevelGroupB.Target));
                }
            }

            Tuple<int, int> validTuple = LevelGroup.DefaultFactory.ValidLevelGroups.First();

            //verify true for same object
            LevelGroup sameRefVerifier = LevelGroup.DefaultFactory.GetLevelGroup(validTuple.Item1, validTuple.Item2);
            Object sameLevelGroup = sameRefVerifier;
            Assert.IsTrue(sameRefVerifier.Equals(sameLevelGroup));

            //verify that fasle returned for null
            LevelGroup nullVerifier = LevelGroup.DefaultFactory.GetLevelGroup(validTuple.Item1, validTuple.Item2);
            Object nullObject = null;
            Assert.IsFalse(nullVerifier.Equals(nullObject));

            //verify false is returned for non-thLevelGroup objects
            LevelGroup otherObjectVerifier = LevelGroup.DefaultFactory.GetLevelGroup(validTuple.Item1, validTuple.Item2);
            Object otherObject = new Object();

            Assert.IsFalse(otherObjectVerifier.Equals(otherObject));
        }

        
        [TestMethod]
        public void LevelGroupIEquatableEquals()
        {
            //verify that all equal object return true for Equals (non-generic)
            foreach (var tuple in LevelGroup.DefaultFactory.ValidLevelGroups)
            {
                PrivateObject privateLevelGroupA = new PrivateObject(typeof(LevelGroup), tuple.Item1, tuple.Item2);
                PrivateObject privateLevelGroupB = new PrivateObject(typeof(LevelGroup), tuple.Item1, tuple.Item2);

                LevelGroup levelGroupA = (LevelGroup)privateLevelGroupA.Target;
                LevelGroup levelGroupB = (LevelGroup)privateLevelGroupB.Target;

                Assert.IsTrue(levelGroupA.Equals(levelGroupB));
            }

            //verify that all non-equal return false for Equals (non-generic)
            foreach (var i in LevelGroup.DefaultFactory.ValidLevelGroups)
            {
                foreach (var j in LevelGroup.DefaultFactory.ValidLevelGroups)
                {
                    if (i == j) continue;

                    PrivateObject privateLevelGroupA = new PrivateObject(typeof(LevelGroup), i.Item1, i.Item2);
                    PrivateObject privateLevelGroupB = new PrivateObject(typeof(LevelGroup), j.Item1, j.Item2);

                    LevelGroup levelGroupA = (LevelGroup)privateLevelGroupA.Target;
                    LevelGroup levelGroupB = (LevelGroup)privateLevelGroupB.Target;

                    Assert.IsFalse(levelGroupA.Equals(levelGroupB));
                }
            }

            Tuple<int, int> validTuple = LevelGroup.DefaultFactory.ValidLevelGroups.First();

            //verify true for same object
            LevelGroup sameRefVerifier = LevelGroup.DefaultFactory.GetLevelGroup(validTuple.Item1, validTuple.Item2);
            Assert.IsTrue(sameRefVerifier.Equals(sameRefVerifier));

            //verify that fasle returned for null
            LevelGroup nullVerifier = LevelGroup.DefaultFactory.GetLevelGroup(validTuple.Item1, validTuple.Item2);
            LevelGroup nullLevelGroup = null;
            Assert.IsFalse(nullVerifier.Equals(nullLevelGroup));
        }

        
        [TestMethod]
        public void LevelGroupGetHashCode()
        {
            Dictionary<int, LevelGroup> levelGroupDict = new Dictionary<int, LevelGroup>();

            //verify that no two levelGroups have same hash code
            foreach (var tuple in LevelGroup.DefaultFactory.ValidLevelGroups)
            {
                LevelGroup levelGroup = LevelGroup.DefaultFactory.GetLevelGroup(tuple.Item1, tuple.Item2);
                Assert.IsFalse(levelGroupDict.Keys.Contains(levelGroup.GetHashCode()));

                levelGroupDict[levelGroup.GetHashCode()] = levelGroup;
            }
        }

        
        [TestMethod]
        public void LevelGroupObjectCompareTo()
        {
            foreach (var i in LevelGroup.DefaultFactory.ValidLevelGroups)
            {
                foreach (var j in LevelGroup.DefaultFactory.ValidLevelGroups)
                {
                    LevelGroup iLevelGroup = LevelGroup.DefaultFactory.GetLevelGroup(i.Item1, i.Item2);
                    LevelGroup jLevelGroup = LevelGroup.DefaultFactory.GetLevelGroup(j.Item1, j.Item2);

                    int i2j = iLevelGroup.CompareTo((Object)jLevelGroup);
                    int j2i = jLevelGroup.CompareTo((Object)iLevelGroup);

                    if (i.Item1 < j.Item1)
                    {
                        Assert.IsTrue(i2j < 0);
                        Assert.IsTrue(j2i > 0);
                    }
                    else if (i.Item1 > j.Item1)
                    {
                        Assert.IsTrue(i2j > 0);
                        Assert.IsTrue(j2i < 0);
                    }
                    else if(i.Item1 == j.Item1 && i.Item2 == j.Item2)
                    {
                        Assert.AreEqual(0, i2j);
                        Assert.AreEqual(0, j2i);
                    }

                }
            }

        }

        
        [TestMethod]
        public void LevelGroupGenericCompareTo()
        {
            foreach (var i in LevelGroup.DefaultFactory.ValidLevelGroups)
            {
                foreach (var j in LevelGroup.DefaultFactory.ValidLevelGroups)
                {
                    LevelGroup iLevelGroup = LevelGroup.DefaultFactory.GetLevelGroup(i.Item1, i.Item2);
                    LevelGroup jLevelGroup = LevelGroup.DefaultFactory.GetLevelGroup(j.Item1, j.Item2);

                    int i2j = iLevelGroup.CompareTo(jLevelGroup);
                    int j2i = jLevelGroup.CompareTo(iLevelGroup);

                    if (i.Item1 < j.Item1)
                    {
                        Assert.IsTrue(i2j < 0);
                        Assert.IsTrue(j2i > 0);
                    }
                    else if (i.Item1 > j.Item1)
                    {
                        Assert.IsTrue(i2j > 0);
                        Assert.IsTrue(j2i < 0);
                    }
                    else if(i.Item1 == j.Item1 && i.Item2 == j.Item2)
                    {
                        Assert.AreEqual(0, i2j);
                        Assert.AreEqual(0, j2i);
                    }

                }
            }
        }

        
        [TestMethod]
        public void LevelGroupComaprisonOperators()
        {
            foreach (var i in LevelGroup.DefaultFactory.ValidLevelGroups)
            {
                foreach (var j in LevelGroup.DefaultFactory.ValidLevelGroups)
                {
                    LevelGroup iLevelGroup = LevelGroup.DefaultFactory.GetLevelGroup(i.Item1, i.Item2);
                    LevelGroup jLevelGroup = LevelGroup.DefaultFactory.GetLevelGroup(j.Item1, j.Item2);

                    Assert.AreEqual(i != j, iLevelGroup != jLevelGroup);
                    Assert.AreEqual(i == j, iLevelGroup == jLevelGroup);
                    Assert.AreEqual(i.Item1 <  j.Item1, iLevelGroup < jLevelGroup);
                    Assert.AreEqual(i.Item1 <= j.Item1, iLevelGroup <= jLevelGroup);
                    Assert.AreEqual(i.Item1 >  j.Item1, iLevelGroup > jLevelGroup);
                    Assert.AreEqual(i.Item1 >= j.Item1, iLevelGroup >= jLevelGroup);

                    Assert.AreEqual(j != i, jLevelGroup != iLevelGroup);
                    Assert.AreEqual(j == i, jLevelGroup == iLevelGroup);
                    Assert.AreEqual(j.Item1 <  i.Item1, jLevelGroup < iLevelGroup);
                    Assert.AreEqual(j.Item1 <= i.Item1, jLevelGroup <= iLevelGroup);
                    Assert.AreEqual(j.Item1 >  i.Item1, jLevelGroup > iLevelGroup);
                    Assert.AreEqual(j.Item1 >= i.Item1, jLevelGroup >= iLevelGroup);
                }
            }

            Tuple<int, int> validTuple = LevelGroup.DefaultFactory.ValidLevelGroups.First();

            LevelGroup iNullLevelGroup = null;
            LevelGroup jNullLevelGroup = null;
            LevelGroup nonNullLevelGroup = LevelGroup.DefaultFactory.GetLevelGroup(validTuple.Item1, validTuple.Item2);

            Assert.IsTrue(iNullLevelGroup == jNullLevelGroup);
            Assert.IsTrue(jNullLevelGroup == iNullLevelGroup);

            Assert.IsFalse(iNullLevelGroup != jNullLevelGroup);
            Assert.IsFalse(jNullLevelGroup != iNullLevelGroup);

            Assert.IsFalse(nonNullLevelGroup == iNullLevelGroup);
            Assert.IsFalse(iNullLevelGroup == nonNullLevelGroup);

            Assert.IsTrue(nonNullLevelGroup != iNullLevelGroup);
            Assert.IsTrue(iNullLevelGroup != nonNullLevelGroup);
        }

        
        [TestMethod]
        public void LevelGroupSerialization()
        {
            BinaryFormatter formatter = new BinaryFormatter();

            foreach (var tuple in LevelGroup.DefaultFactory.ValidLevelGroups)
            {
                using (MemoryStream memStream = new MemoryStream())
                {
                    LevelGroup levelGroup = LevelGroup.DefaultFactory.GetLevelGroup(tuple.Item1, tuple.Item2);

                    formatter.Serialize(memStream, levelGroup);
                    memStream.Position = 0;
                    LevelGroup deserializedLevelGroup = (LevelGroup)formatter.Deserialize(memStream);

                    Assert.AreEqual(levelGroup.MinLevel, deserializedLevelGroup.MinLevel);
                    Assert.AreEqual(levelGroup.MaxLevel, deserializedLevelGroup.MaxLevel);                    
                }
            }
        }
    }
}
